#include <GL/gl.h>

#include <cmath>
#include "cylinderPrimitive.h"
#include "defines/primitive.h"
#include "defines/axes.h"
#include "defines/octreeType.h"

#define THETA 18

CylinderPrimitive::CylinderPrimitive(int _id) : Object(_id){
}

CylinderPrimitive::CylinderPrimitive(const Vector3D &_center, double _radius, double _height, int _id)
    : Object(_id, CYLINDER, true), center(_center), radius(_radius), height(_height){
}

CylinderPrimitive::CylinderPrimitive(const Vector3D &_center, double _radius, double _height, const Color &_c, int _id)
    : Object(_c, _id, CYLINDER, true), center(_center), radius(_radius), height(_height){
}

CylinderPrimitive::~CylinderPrimitive(){
}

Vector3D CylinderPrimitive::getCenter() const{
    return center;
}

void CylinderPrimitive::setCenter(const Vector3D &_c){
    center = _c;
}

double CylinderPrimitive::getRadius() const{
    return radius;
}

void CylinderPrimitive::setRadius(double _r){
    radius = _r;
}

double CylinderPrimitive::getHeight() const{
    return height;
}

void CylinderPrimitive::setHeight(double _h){
    height = _h;
}

double CylinderPrimitive::volume() const{
    return M_PI*radius*radius*height;
}

double CylinderPrimitive::area() const{
    return (M_PI*radius*radius*2.0) + (M_PI*radius*height*2.0);
}

void CylinderPrimitive::draw(bool _wireframe, bool _useParentColor, const Color &_parentColor) const{
    if(_wireframe){
        glDisable(GL_LIGHT0);
        glDisable(GL_LIGHTING);

        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
        glDisable(GL_CULL_FACE);

        glLineWidth(2);
    }

	glPushMatrix();
	glTranslated(center.x, center.y, center.z);
	glRotated(rotationX * 90, 1.0, 0.0, 0.0);
	glRotated(rotationY * 90, 0.0, 1.0, 0.0);
	glRotated(rotationZ * 90, 0.0, 0.0, 1.0);

    Color color_ = chooseColor(_wireframe, _useParentColor, _parentColor);
    glColor4d(color_.red, color_.green, color_.blue, color_.alpha);


	//desenhando a base em cima
    glBegin(GL_POLYGON);
    glNormal3f(0, 0, 1);

    for(double i=0; i<=360.0; i = i+THETA){
        double theta = toRad(i);
        double x = radius*cos(theta);
        double y = radius*sin(theta);

        glVertex3f(x, y, height);
    }
    glEnd();


    glBegin(GL_QUAD_STRIP);
    Vector3D p1, p2, p3, normal;
    for(double i=0; i<=360.0; i = i+THETA){
        double theta = toRad(i);

        double x = radius*cos(theta);
        double y = radius*sin(theta);
        p1 = Vector3D(x, y, height);
        p2 = Vector3D(x, y, 0);

        theta = toRad(i + THETA);
        x = radius*cos(theta);
        y = radius*sin(theta);
        p3 = Vector3D(x, y, center.z + height);

        normal = ((Vector3D(0, 0, -1)).crossProduct(p3-p1)).normalize();

        glNormal3f(normal.x, normal.y, normal.z);

        glVertex3f(p1.x, p1.y, p1.z);
        glVertex3f(p2.x, p2.y, p2.z);
    }
    glEnd();

	//desenhando a base em baixo
    glBegin(GL_POLYGON);
    glNormal3f(0, 0, -1);

    for(double i=0; i<=360.0; i = i+THETA){
        double theta = toRad(-i);
        double x = radius*cos(theta);
        double y = radius*sin(theta);

        glVertex3f(x, y, 0);
    }
    glEnd();


	glPopMatrix();


    if(_wireframe){
        glLineWidth(1);

        glEnable(GL_LIGHT0);
        glEnable(GL_LIGHTING);

        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
        glEnable(GL_CULL_FACE);
    }

	glPushMatrix();
	glTranslated(center.x, center.y, center.z);

    if(selected){
        Vector3D min, max;
        getMinMax(&min.x, &max.x, X_AXIS);
        getMinMax(&min.y, &max.y, Y_AXIS);
        getMinMax(&min.z, &max.z, Z_AXIS);

        drawBox(true, min - center, max - center);
    }
	glPopMatrix();
}


int CylinderPrimitive::classify(Vector3D *_octreePoint) const{
    //casos basicos de WHITE
    //acima ou abaixo do cilindro
    Vector3D min = _octreePoint[0], max = _octreePoint[7];

    bool minZUP = (min.z >= center.z + height);
    bool maxZDown = (max.z <= center.z);

    if(minZUP || maxZDown) return WHITE;

    double radius_2 = radius * radius;
    double minDistance_2;

    if((!minZUP && (min.z >= center.z)) || (!maxZDown && (max.z <= center.z + height))){
        //tah no meio
        bool allPointsOutside = true;
        bool allPointsInside = true;

        for(int i = 0; i < 8; i++){
            minDistance_2 = ((_octreePoint[i].x - center.x) * (_octreePoint[i].x - center.x))
                          + ((_octreePoint[i].y - center.y) * (_octreePoint[i].y - center.y));
            if( minDistance_2 < radius_2 ){
                allPointsOutside = false;
            }else{
                allPointsInside = false;
            }
        }
        if(allPointsInside) return BLACK;   //todos os pontos dentros
        if(!allPointsOutside) return GREY;    //nem todos os pontos dentro (sedo algum do lado de fora)
    }

    //pegando os casos WHITE
    minDistance_2 = 0; //a menor distancia entre o centro da esfera ao cubo ao quadrado

    if(center.x < min.x){
        minDistance_2 += (min.x - center.x) * (min.x - center.x);
    }else if(center.x > max.x){
        minDistance_2 += (max.x - center.x) * (max.x - center.x);
    }
    if(center.y < min.y){
        minDistance_2 += (min.y - center.y) * (min.y - center.y);
    }else if(center.y > max.y){
        minDistance_2 += (max.y - center.y) * (max.y - center.y);
    }

    if(minDistance_2 >= radius_2) return WHITE;

    return GREY;
}

void CylinderPrimitive::getMinMax(double *_min, double *_max, unsigned int _axis) const{
    switch(_axis){
        case X_AXIS:{
            *_min = center.x - radius;
            *_max = center.x + radius;
            break;
        }
        case Y_AXIS:{
            *_min = center.y - radius;
            *_max = center.y + radius;
            break;
        }
        case Z_AXIS:{
            *_min = center.z;
            *_max = center.z + height;
            break;
        }
    }
}
